---
title: "Mobile App Development with Kotlin"
description: "Learn Kotlin mobile app development with Jetpack Compose, comparing with JavaScript mobile development patterns and React Native concepts"
---

# Mobile App Development with Kotlin

Welcome to the eighth module of JavaScript to Kotlin conversion! In this module, we'll explore mobile app development with Kotlin using Jetpack Compose and understand how it compares to JavaScript mobile development patterns like React Native. We'll learn about modern UI development, state management, navigation, and mobile app architecture.

## Learning Objectives

By the end of this module, you will be able to:
- Understand Jetpack Compose and declarative UI development
- Compare Kotlin mobile development with React Native patterns
- Implement state management with ViewModel and StateFlow
- Create navigation flows with Navigation Compose
- Handle data persistence and local storage
- Implement network requests and API integration
- Build responsive and accessible mobile interfaces
- Apply modern mobile development best practices

## Jetpack Compose Overview

### Declarative UI Comparison

Jetpack Compose is Kotlin's equivalent to React Native's declarative UI approach, but with native Android performance and better type safety.

<UniversalEditor title="UI Framework Comparison" compare={true}>
```javascript !! js
// React Native - JavaScript mobile UI
import React, { useState } from 'react';
import { View, Text, Button, StyleSheet } from 'react-native';

const CounterScreen = () => {
  const [count, setCount] = useState(0);
  
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Counter App</Text>
      <Text style={styles.counter}>Count: {count}</Text>
      <Button 
        title="Increment" 
        onPress={() => setCount(count + 1)} 
      />
      <Button 
        title="Decrement" 
        onPress={() => setCount(count - 1)} 
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    padding: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
  },
  counter: {
    fontSize: 18,
    marginBottom: 20,
  },
});

export default CounterScreen;
```

```kotlin !! kt
// Jetpack Compose - Kotlin mobile UI
package com.example.mobileapp

import androidx.compose.foundation.layout.*
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp

@Composable
fun CounterScreen() {
    var count by remember { mutableStateOf(0) }
    
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(
            text = "Counter App",
            style = MaterialTheme.typography.headlineMedium
        )
        
        Spacer(modifier = Modifier.height(16.dp))
        
        Text(
            text = "Count: $count",
            style = MaterialTheme.typography.bodyLarge
        )
        
        Spacer(modifier = Modifier.height(16.dp))
        
        Row(
            horizontalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            Button(
                onClick = { count++ }
            ) {
                Text("Increment")
            }
            
            Button(
                onClick = { count-- }
            ) {
                Text("Decrement")
            }
        }
    }
}
```
</UniversalEditor>

### Key Differences

| Aspect | JavaScript (React Native) | Kotlin (Jetpack Compose) |
|--------|-------------------------|---------------------------|
| **Performance** | JavaScript bridge overhead | Native performance |
| **Type Safety** | Dynamic typing | Static typing with compile-time checks |
| **UI Components** | Cross-platform components | Native Android components |
| **State Management** | useState, Redux | StateFlow, ViewModel |
| **Navigation** | React Navigation | Navigation Compose |
| **Platform Access** | Limited native access | Full Android API access |

## State Management

### State Management Patterns

Let's compare state management approaches in both ecosystems.

<UniversalEditor title="State Management Comparison" compare={true}>
```javascript !! js
// React Native with Redux
import React from 'react';
import { useSelector, useDispatch } from 'react-redux';
import { View, Text, Button } from 'react-native';

// Redux slice
const counterSlice = createSlice({
  name: 'counter',
  initialState: {
    count: 0,
    isLoading: false
  },
  reducers: {
    increment: (state) => {
      state.count += 1;
    },
    decrement: (state) => {
      state.count -= 1;
    },
    setLoading: (state, action) => {
      state.isLoading = action.payload;
    }
  }
});

// Component
const CounterScreen = () => {
  const { count, isLoading } = useSelector(state => state.counter);
  const dispatch = useDispatch();
  
  const handleIncrement = () => {
    dispatch(counterSlice.actions.increment());
  };
  
  const handleDecrement = () => {
    dispatch(counterSlice.actions.decrement());
  };
  
  return (
    <View>
      <Text>Count: {count}</Text>
      <Button title="Increment" onPress={handleIncrement} />
      <Button title="Decrement" onPress={handleDecrement} />
      {isLoading && <Text>Loading...</Text>}
    </View>
  );
};
```

```kotlin !! kt
// Kotlin with ViewModel and StateFlow
package com.example.mobileapp

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch

class CounterViewModel : ViewModel() {
    
    private val _uiState = MutableStateFlow(CounterUiState())
    val uiState: StateFlow<CounterUiState> = _uiState.asStateFlow()
    
    fun increment() {
        _uiState.value = _uiState.value.copy(
            count = _uiState.value.count + 1
        )
    }
    
    fun decrement() {
        _uiState.value = _uiState.value.copy(
            count = _uiState.value.count - 1
        )
    }
    
    fun setLoading(isLoading: Boolean) {
        _uiState.value = _uiState.value.copy(
            isLoading = isLoading
        )
    }
}

data class CounterUiState(
    val count: Int = 0,
    val isLoading: Boolean = false
)

// Composable
@Composable
fun CounterScreen(
    viewModel: CounterViewModel = viewModel()
) {
    val uiState by viewModel.uiState.collectAsState()
    
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(
            text = "Count: ${uiState.count}",
            style = MaterialTheme.typography.headlineMedium
        )
        
        Spacer(modifier = Modifier.height(16.dp))
        
        Row(
            horizontalArrangement = Arrangement.spacedBy(8.dp)
        ) {
            Button(
                onClick = { viewModel.increment() }
            ) {
                Text("Increment")
            }
            
            Button(
                onClick = { viewModel.decrement() }
            ) {
                Text("Decrement")
            }
        }
        
        if (uiState.isLoading) {
            CircularProgressIndicator()
        }
    }
}
```
</UniversalEditor>

## Navigation

### Navigation Patterns

<UniversalEditor title="Navigation Implementation" compare={true}>
```javascript !! js
// React Navigation
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

const Stack = createStackNavigator();

const App = () => {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen 
          name="Home" 
          component={HomeScreen}
          options={{ title: 'Home' }}
        />
        <Stack.Screen 
          name="Details" 
          component={DetailsScreen}
          options={{ title: 'Details' }}
        />
      </Stack.Navigator>
    </NavigationContainer>
  );
};

const HomeScreen = ({ navigation }) => {
  return (
    <View>
      <Text>Home Screen</Text>
      <Button 
        title="Go to Details" 
        onPress={() => navigation.navigate('Details', { id: 1 })} 
      />
    </View>
  );
};

const DetailsScreen = ({ route, navigation }) => {
  const { id } = route.params;
  
  return (
    <View>
      <Text>Details Screen - ID: {id}</Text>
      <Button 
        title="Go Back" 
        onPress={() => navigation.goBack()} 
      />
    </View>
  );
};
```

```kotlin !! kt
// Navigation Compose
package com.example.mobileapp

import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.composable
import androidx.navigation.compose.rememberNavController

@Composable
fun AppNavigation() {
    val navController = rememberNavController()
    
    NavHost(
        navController = navController,
        startDestination = "home"
    ) {
        composable("home") {
            HomeScreen(
                onNavigateToDetails = { id ->
                    navController.navigate("details/$id")
                }
            )
        }
        
        composable(
            route = "details/{id}",
            arguments = listOf(
                navArgument("id") { type = NavType.IntType }
            )
        ) { backStackEntry ->
            val id = backStackEntry.arguments?.getInt("id") ?: 0
            DetailsScreen(
                id = id,
                onNavigateBack = {
                    navController.popBackStack()
                }
            )
        }
    }
}

@Composable
fun HomeScreen(
    onNavigateToDetails: (Int) -> Unit
) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(
            text = "Home Screen",
            style = MaterialTheme.typography.headlineMedium
        )
        
        Spacer(modifier = Modifier.height(16.dp))
        
        Button(
            onClick = { onNavigateToDetails(1) }
        ) {
            Text("Go to Details")
        }
    }
}

@Composable
fun DetailsScreen(
    id: Int,
    onNavigateBack: () -> Unit
) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(
            text = "Details Screen - ID: $id",
            style = MaterialTheme.typography.headlineMedium
        )
        
        Spacer(modifier = Modifier.height(16.dp))
        
        Button(
            onClick = onNavigateBack
        ) {
            Text("Go Back")
        }
    }
}
```
</UniversalEditor>

## Data Persistence

### Local Storage Comparison

<UniversalEditor title="Data Persistence Comparison" compare={true}>
```javascript !! js
// React Native with AsyncStorage
import AsyncStorage from '@react-native-async-storage/async-storage';

// Store data
const storeData = async (key, value) => {
  try {
    await AsyncStorage.setItem(key, JSON.stringify(value));
  } catch (error) {
    console.error('Error storing data:', error);
  }
};

// Retrieve data
const getData = async (key) => {
  try {
    const value = await AsyncStorage.getItem(key);
    return value != null ? JSON.parse(value) : null;
  } catch (error) {
    console.error('Error retrieving data:', error);
    return null;
  }
};

// Usage in component
const UserProfileScreen = () => {
  const [user, setUser] = useState(null);
  
  useEffect(() => {
    const loadUser = async () => {
      const userData = await getData('user');
      setUser(userData);
    };
    loadUser();
  }, []);
  
  const saveUser = async (userData) => {
    await storeData('user', userData);
    setUser(userData);
  };
  
  return (
    <View>
      {user ? (
        <Text>Welcome, {user.name}!</Text>
      ) : (
        <Text>No user data</Text>
      )}
    </View>
  );
};
```

```kotlin !! kt
// Kotlin with Room Database
package com.example.mobileapp

import androidx.room.*
import kotlinx.coroutines.flow.Flow

@Entity(tableName = "users")
data class User(
    @PrimaryKey val id: Int,
    val name: String,
    val email: String,
    val avatar: String?
)

@Dao
interface UserDao {
    @Query("SELECT * FROM users WHERE id = :userId")
    fun getUserById(userId: Int): Flow<User?>
    
    @Insert(onConflict = OnConflictStrategy.REPLACE)
    suspend fun insertUser(user: User)
    
    @Delete
    suspend fun deleteUser(user: User)
}

@Database(entities = [User::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun userDao(): UserDao
}

// Repository
class UserRepository(private val userDao: UserDao) {
    
    fun getUserById(userId: Int): Flow<User?> {
        return userDao.getUserById(userId)
    }
    
    suspend fun saveUser(user: User) {
        userDao.insertUser(user)
    }
    
    suspend fun deleteUser(user: User) {
        userDao.deleteUser(user)
    }
}

// ViewModel
class UserProfileViewModel(
    private val userRepository: UserRepository
) : ViewModel() {
    
    private val _userId = MutableStateFlow(1)
    val userId: StateFlow<Int> = _userId.asStateFlow()
    
    val user: StateFlow<User?> = userId.flatMapLatest { id ->
        userRepository.getUserById(id)
    }.stateIn(
        scope = viewModelScope,
        started = SharingStarted.WhileSubscribed(5000),
        initialValue = null
    )
    
    fun saveUser(user: User) {
        viewModelScope.launch {
            userRepository.saveUser(user)
        }
    }
}

// Composable
@Composable
fun UserProfileScreen(
    viewModel: UserProfileViewModel = viewModel()
) {
    val user by viewModel.user.collectAsState()
    
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        if (user != null) {
            Text(
                text = "Welcome, ${user!!.name}!",
                style = MaterialTheme.typography.headlineMedium
            )
            
            Text(
                text = user!!.email,
                style = MaterialTheme.typography.bodyLarge
            )
        } else {
            Text(
                text = "No user data",
                style = MaterialTheme.typography.bodyLarge
            )
        }
    }
}
```
</UniversalEditor>

## Network Requests

### API Integration Comparison

<UniversalEditor title="Network Requests Comparison" compare={true}>
```javascript !! js
// React Native with fetch
const API_BASE_URL = 'https://api.example.com';

const apiService = {
  async getUsers() {
    try {
      const response = await fetch(`${API_BASE_URL}/users`);
      if (!response.ok) {
        throw new Error('Network response was not ok');
      }
      return await response.json();
    } catch (error) {
      console.error('Error fetching users:', error);
      throw error;
    }
  },
  
  async createUser(userData) {
    try {
      const response = await fetch(`${API_BASE_URL}/users`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/json',
        },
        body: JSON.stringify(userData),
      });
      if (!response.ok) {
        throw new Error('Network response was not ok');
      }
      return await response.json();
    } catch (error) {
      console.error('Error creating user:', error);
      throw error;
    }
  }
};

// Usage in component
const UsersScreen = () => {
  const [users, setUsers] = useState([]);
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState(null);
  
  useEffect(() => {
    const fetchUsers = async () => {
      setLoading(true);
      setError(null);
      try {
        const data = await apiService.getUsers();
        setUsers(data);
      } catch (err) {
        setError(err.message);
      } finally {
        setLoading(false);
      }
    };
    
    fetchUsers();
  }, []);
  
  if (loading) return <Text>Loading...</Text>;
  if (error) return <Text>Error: {error}</Text>;
  
  return (
    <View>
      {users.map(user => (
        <Text key={user.id}>{user.name}</Text>
      ))}
    </View>
  );
};
```

```kotlin !! kt
// Kotlin with Retrofit and Coroutines
package com.example.mobileapp

import retrofit2.http.*
import retrofit2.Response

// API interface
interface ApiService {
    @GET("users")
    suspend fun getUsers(): Response<List<User>>
    
    @POST("users")
    suspend fun createUser(@Body user: CreateUserRequest): Response<User>
}

// Repository
class UserRepository(
    private val apiService: ApiService
) {
    
    suspend fun getUsers(): Result<List<User>> {
        return try {
            val response = apiService.getUsers()
            if (response.isSuccessful) {
                Result.success(response.body() ?: emptyList())
            } else {
                Result.failure(Exception("Network error: ${response.code()}"))
            }
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
    
    suspend fun createUser(user: CreateUserRequest): Result<User> {
        return try {
            val response = apiService.createUser(user)
            if (response.isSuccessful) {
                Result.success(response.body()!!)
            } else {
                Result.failure(Exception("Network error: ${response.code()}"))
            }
        } catch (e: Exception) {
            Result.failure(e)
        }
    }
}

// ViewModel
class UsersViewModel(
    private val userRepository: UserRepository
) : ViewModel() {
    
    private val _uiState = MutableStateFlow(UsersUiState())
    val uiState: StateFlow<UsersUiState> = _uiState.asStateFlow()
    
    init {
        loadUsers()
    }
    
    fun loadUsers() {
        viewModelScope.launch {
            _uiState.value = _uiState.value.copy(isLoading = true)
            
            userRepository.getUsers()
                .onSuccess { users ->
                    _uiState.value = _uiState.value.copy(
                        users = users,
                        isLoading = false,
                        error = null
                    )
                }
                .onFailure { error ->
                    _uiState.value = _uiState.value.copy(
                        isLoading = false,
                        error = error.message
                    )
                }
        }
    }
}

data class UsersUiState(
    val users: List<User> = emptyList(),
    val isLoading: Boolean = false,
    val error: String? = null
)

data class CreateUserRequest(
    val name: String,
    val email: String
)

// Composable
@Composable
fun UsersScreen(
    viewModel: UsersViewModel = viewModel()
) {
    val uiState by viewModel.uiState.collectAsState()
    
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp)
    ) {
        when {
            uiState.isLoading -> {
                CircularProgressIndicator(
                    modifier = Modifier.align(Alignment.CenterHorizontally)
                )
            }
            uiState.error != null -> {
                Text(
                    text = "Error: ${uiState.error}",
                    color = MaterialTheme.colorScheme.error
                )
            }
            else -> {
                LazyColumn {
                    items(uiState.users) { user ->
                        UserItem(user = user)
                    }
                }
            }
        }
    }
}

@Composable
fun UserItem(user: User) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(vertical = 4.dp)
    ) {
        Column(
            modifier = Modifier.padding(16.dp)
        ) {
            Text(
                text = user.name,
                style = MaterialTheme.typography.titleMedium
            )
            Text(
                text = user.email,
                style = MaterialTheme.typography.bodyMedium
            )
        }
    }
}
```
</UniversalEditor>

## Practice Exercises

### Exercise 1: Todo List App
Create a complete todo list app with local storage and state management.

<UniversalEditor title="Todo List App Exercise" compare={true}>
```javascript !! js
// React Native Todo App
import React, { useState, useEffect } from 'react';
import { View, Text, TextInput, Button, FlatList, StyleSheet } from 'react-native';
import AsyncStorage from '@react-native-async-storage/async-storage';

const TodoApp = () => {
  const [todos, setTodos] = useState([]);
  const [newTodo, setNewTodo] = useState('');
  const [loading, setLoading] = useState(true);
  
  useEffect(() => {
    loadTodos();
  }, []);
  
  const loadTodos = async () => {
    try {
      const storedTodos = await AsyncStorage.getItem('todos');
      if (storedTodos) {
        setTodos(JSON.parse(storedTodos));
      }
    } catch (error) {
      console.error('Error loading todos:', error);
    } finally {
      setLoading(false);
    }
  };
  
  const saveTodos = async (newTodos) => {
    try {
      await AsyncStorage.setItem('todos', JSON.stringify(newTodos));
    } catch (error) {
      console.error('Error saving todos:', error);
    }
  };
  
  const addTodo = () => {
    if (newTodo.trim()) {
      const todo = {
        id: Date.now(),
        text: newTodo.trim(),
        completed: false
      };
      const updatedTodos = [...todos, todo];
      setTodos(updatedTodos);
      saveTodos(updatedTodos);
      setNewTodo('');
    }
  };
  
  const toggleTodo = (id) => {
    const updatedTodos = todos.map(todo =>
      todo.id === id ? { ...todo, completed: !todo.completed } : todo
    );
    setTodos(updatedTodos);
    saveTodos(updatedTodos);
  };
  
  const deleteTodo = (id) => {
    const updatedTodos = todos.filter(todo => todo.id !== id);
    setTodos(updatedTodos);
    saveTodos(updatedTodos);
  };
  
  if (loading) {
    return (
      <View style={styles.container}>
        <Text>Loading...</Text>
      </View>
    );
  }
  
  return (
    <View style={styles.container}>
      <Text style={styles.title}>Todo List</Text>
      
      <View style={styles.inputContainer}>
        <TextInput
          style={styles.input}
          value={newTodo}
          onChangeText={setNewTodo}
          placeholder="Add a new todo"
        />
        <Button title="Add" onPress={addTodo} />
      </View>
      
      <FlatList
        data={todos}
        keyExtractor={(item) => item.id.toString()}
        renderItem={({ item }) => (
          <View style={styles.todoItem}>
            <Text
              style={[
                styles.todoText,
                item.completed && styles.completedText
              ]}
              onPress={() => toggleTodo(item.id)}
            >
              {item.text}
            </Text>
            <Button
              title="Delete"
              onPress={() => deleteTodo(item.id)}
            />
          </View>
        )}
      />
    </View>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
  },
  title: {
    fontSize: 24,
    fontWeight: 'bold',
    marginBottom: 20,
  },
  inputContainer: {
    flexDirection: 'row',
    marginBottom: 20,
  },
  input: {
    flex: 1,
    borderWidth: 1,
    borderColor: '#ccc',
    padding: 10,
    marginRight: 10,
  },
  todoItem: {
    flexDirection: 'row',
    alignItems: 'center',
    padding: 10,
    borderBottomWidth: 1,
    borderBottomColor: '#eee',
  },
  todoText: {
    flex: 1,
    fontSize: 16,
  },
  completedText: {
    textDecorationLine: 'line-through',
    color: '#888',
  },
});
```

```kotlin !! kt
// Kotlin Todo App with Compose
package com.example.mobileapp

import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.items
import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.launch

data class Todo(
    val id: Long,
    val text: String,
    val completed: Boolean = false
)

class TodoViewModel : ViewModel() {
    
    private val _todos = MutableStateFlow<List<Todo>>(emptyList())
    val todos: StateFlow<List<Todo>> = _todos.asStateFlow()
    
    private val _newTodoText = MutableStateFlow("")
    val newTodoText: StateFlow<String> = _newTodoText.asStateFlow()
    
    init {
        loadTodos()
    }
    
    fun updateNewTodoText(text: String) {
        _newTodoText.value = text
    }
    
    fun addTodo() {
        val text = _newTodoText.value.trim()
        if (text.isNotEmpty()) {
            val todo = Todo(
                id = System.currentTimeMillis(),
                text = text
            )
            _todos.value = _todos.value + todo
            _newTodoText.value = ""
            saveTodos()
        }
    }
    
    fun toggleTodo(id: Long) {
        _todos.value = _todos.value.map { todo ->
            if (todo.id == id) {
                todo.copy(completed = !todo.completed)
            } else {
                todo
            }
        }
        saveTodos()
    }
    
    fun deleteTodo(id: Long) {
        _todos.value = _todos.value.filter { it.id != id }
        saveTodos()
    }
    
    private fun loadTodos() {
        viewModelScope.launch {
            // In real app, load from Room database
            // For now, use empty list
            _todos.value = emptyList()
        }
    }
    
    private fun saveTodos() {
        viewModelScope.launch {
            // In real app, save to Room database
            // For now, just update state
        }
    }
}

@Composable
fun TodoApp(
    viewModel: TodoViewModel = viewModel()
) {
    val todos by viewModel.todos.collectAsState()
    val newTodoText by viewModel.newTodoText.collectAsState()
    
    Column(
        modifier = Modifier
            .fillMaxSize()
            .padding(16.dp)
    ) {
        Text(
            text = "Todo List",
            style = MaterialTheme.typography.headlineMedium,
            modifier = Modifier.padding(bottom = 16.dp)
        )
        
        // Input section
        Row(
            modifier = Modifier.fillMaxWidth(),
            verticalAlignment = Alignment.CenterVertically
        ) {
            OutlinedTextField(
                value = newTodoText,
                onValueChange = { viewModel.updateNewTodoText(it) },
                label = { Text("Add a new todo") },
                modifier = Modifier.weight(1f),
                singleLine = true
            )
            
            Spacer(modifier = Modifier.width(8.dp))
            
            Button(
                onClick = { viewModel.addTodo() }
            ) {
                Text("Add")
            }
        }
        
        Spacer(modifier = Modifier.height(16.dp))
        
        // Todo list
        LazyColumn {
            items(todos) { todo ->
                TodoItem(
                    todo = todo,
                    onToggle = { viewModel.toggleTodo(todo.id) },
                    onDelete = { viewModel.deleteTodo(todo.id) }
                )
            }
        }
    }
}

@Composable
fun TodoItem(
    todo: Todo,
    onToggle: () -> Unit,
    onDelete: () -> Unit
) {
    Card(
        modifier = Modifier
            .fillMaxWidth()
            .padding(vertical = 4.dp)
    ) {
        Row(
            modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp),
            verticalAlignment = Alignment.CenterVertically
        ) {
            Text(
                text = todo.text,
                style = MaterialTheme.typography.bodyLarge,
                modifier = Modifier.weight(1f),
                color = if (todo.completed) {
                    MaterialTheme.colorScheme.onSurface.copy(alpha = 0.6f)
                } else {
                    MaterialTheme.colorScheme.onSurface
                }
            )
            
            Spacer(modifier = Modifier.width(8.dp))
            
            Button(
                onClick = onToggle
            ) {
                Text(if (todo.completed) "Undo" else "Complete")
            }
            
            Spacer(modifier = Modifier.width(8.dp))
            
            Button(
                onClick = onDelete
            ) {
                Text("Delete")
            }
        }
    }
}
```
</UniversalEditor>

## Summary

In this module, we've explored mobile app development with Kotlin using Jetpack Compose. Here are the key takeaways:

### Key Concepts Covered
1. **Jetpack Compose**: Modern declarative UI framework for Android
2. **State Management**: ViewModel and StateFlow for reactive state management
3. **Navigation**: Navigation Compose for screen navigation
4. **Data Persistence**: Room database for local storage
5. **Network Requests**: Retrofit and coroutines for API integration
6. **Modern UI**: Material Design 3 components and theming

### JavaScript vs Kotlin Mobile Development

| Aspect | JavaScript (React Native) | Kotlin (Jetpack Compose) |
|--------|-------------------------|---------------------------|
| **Performance** | JavaScript bridge overhead | Native performance |
| **Type Safety** | Dynamic typing | Static typing with compile-time checks |
| **UI Components** | Cross-platform components | Native Android components |
| **State Management** | useState, Redux | StateFlow, ViewModel |
| **Navigation** | React Navigation | Navigation Compose |
| **Platform Access** | Limited native access | Full Android API access |
| **Development Experience** | Hot reload | Hot reload with better tooling |

### Best Practices
1. **Use ViewModel**: Separate business logic from UI components
2. **Implement StateFlow**: For reactive state management
3. **Follow Material Design**: Use Material Design 3 components
4. **Use Room Database**: For local data persistence
5. **Implement Error Handling**: Proper error states and loading indicators
6. **Use Coroutines**: For asynchronous operations
7. **Follow Navigation Patterns**: Use Navigation Compose for screen navigation

### Next Steps
In the next module, we'll explore cross-platform development with Kotlin Multiplatform, focusing on sharing code between Android, iOS, and web platforms.

---

**Practice Challenge**: Build a complete weather app with location services, API integration, local storage for favorites, and a modern Material Design 3 UI using Jetpack Compose. 